﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;
using Infrastructure.Tools;

namespace Infrastructure.BaseClasses
{
    public class ModelBase<T> : INotifyPropertyChanged, INotifyDataErrorInfo, IEditableObject
    {
        #region Variables
        protected readonly Dictionary<String, List<String>> currentErrors =
         new Dictionary<string, List<String>>();
        private Memento<T> memento;
        #endregion

        protected ModelBase()
        {

        }

        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        /// Publikuj zmianę w propercji
        /// </summary>
        /// <param name="propName">nazwa propercji, w której nastąpiła zmiana</param>
        public virtual void RaisePropertyChanged(string propName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }

        public virtual void RaiseAllPropertyChanged()
        {
            PropertyInfo[] propertyInfos = GetType().GetProperties(BindingFlags.Public | BindingFlags.Static);
            
            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                RaisePropertyChanged(propertyInfo.Name);
            }
        }

        protected string GetPropertyName<TProperty>(Expression<Func<TProperty>> property)
        {
            var lambda = (LambdaExpression)property;

            MemberExpression memberExpression;
            if (lambda.Body is UnaryExpression)
            {
                var unaryExpression = (UnaryExpression)lambda.Body;
                memberExpression = (MemberExpression)unaryExpression.Operand;
            }
            else memberExpression = (MemberExpression)lambda.Body;

            return memberExpression.Member.Name;
        }

        public void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)
        {
            RaisePropertyChanged(GetPropertyName(property));
        }
        #endregion

        #region INotifyDataErrorInfo Members
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public System.Collections.IEnumerable GetErrors(string propertyName)
        {
            if (propertyName != null)
            {
                if (currentErrors.ContainsKey(propertyName))
                {
                    return currentErrors[propertyName];
                }
            }
            return null;
        }

        public bool HasErrors
        {
            get
            {
                return (currentErrors.Where(c => c.Value.Count > 0).Count() > 0);
            }
        }

        protected void ManageErrors(string property, List<String> errors)
        {
            var raiseEventErrorsChanged = false;

            if (property == null)
            {
                return;
            }

            if (errors != null)
            {
                if (currentErrors.ContainsKey(property))
                {
                    currentErrors.Remove(property);
                }

                currentErrors.Add(property, errors);
                raiseEventErrorsChanged = true;
            }
            else
            {
                if (currentErrors.ContainsKey(property))
                {
                    currentErrors.Remove(property);
                    raiseEventErrorsChanged = true;
                }
            }

            if (raiseEventErrorsChanged && ErrorsChanged != null)
            {
                ErrorsChanged(this, new DataErrorsChangedEventArgs(property));
            }
        }
        #endregion

        #region IEditableObject Members
        /// <summary>
        /// The wrapped object.
        /// </summary>
        public T Target { get; set; }
        public void BeginEdit()
        {
            if (this.memento == null)
            {
                this.memento = new Memento<T>(this.Target);
            }
        }
        public void CancelEdit()
        {
            if (this.memento != null)
            {
                this.memento.Restore(this.Target);
                this.memento = null;
            }
        }
        public void EndEdit()
        {
            this.memento = null;
        }
        #endregion

        #region Methods
        protected virtual void ValidateProperty<TProperty>(string value, Expression<Func<TProperty>> prop)
        {
        }

        public virtual void ValidateRequired()
        {
        }
        #endregion
    }
}
